package io.spring.sample.graphql.repository;

import io.spring.sample.graphql.repository.base.Company;
import io.spring.sample.graphql.repository.base.Person;
import io.spring.sample.graphql.repository.util.Tool;
import lombok.*;
import org.hibernate.Hibernate;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;

import javax.persistence.*;
import java.time.Instant;
import java.util.Objects;
import java.util.UUID;
//单位是应用系统的泛指概念:　把个人也纳入管理单元。
//设备中的单位， 即可以是公司，也可以是个人。company和person是大数据的影子实体类/只能读。Unit是本地附加的属性。
//搜索引擎ES找到company或者person的id后，就能通过Unit的关联ID和数据库索引快速找到其它相关的字段属性，比如owns设备集合。
//永久单位库JC_PMT_UNT 监察的，土建施工单位：就是简单的新增或更新组织机构代码。

//表实体的名字替换小心：底层数据库的旧的索引FK外键并没有删除掉，可能导致无法跑起来，也不报错！！

/** 特种设备范畴的单位概念；  业务领域专用Unit实体。
 * 单位原本属于监察一体化管理范畴。其功能不一定都落地本平台。本平台单位模型属于附属被动地位。
 * 单位类型和资质证书管理
 * UNT_TYPE_FORCHG = [{id:'USE_UNT_ID',text:'使用单位'},{id:'MANT_UNT_ID',text:'维保单位'},{id:'ALT_UNT_ID',text:'改造单位'},
 * {id:'MAKE_UNT_ID',text:'制造单位'}, {id:'INST_UNT_ID',text:'安装单位'},{id:'OVH_UNT_ID',text:'维修单位'}];
* 检验机构 监察机构 其实自己也算一个Unit机构。
 * 监察机构组织架构特殊：部门？科室？监察机构是按照地理划分的权限(认证归属地？管辖设备{省一级权限一定大于市一级?/区县级/镇所级别})。
 * 监察机构: 省级？ 市级别？ 县级？ 镇级别； 每个级别行政机构都有独立的ID; +配套关系来计算级别或管辖-隶属权限。
 * 行政机关--监察机构自身也可能是个普通的使用单位啊。
 * 所属自贸区ZMQ_COD： 独立监察机构！附加字段： 该单位名下的设备应该归属那个监察机构。
 * JC_UNT_MGE JC_PMT_UNT都没有找到监察机构单位。在JC_ISPUNT独立表。
 * 旧平台检验监察的ID不一样： 检验单位地址反而维护更好？同一个UNT_ORG_COD一样的单位随意增加 UNT_STATE=2注销状态
 *考虑Unit+Company+Person从外部平台同步数据而来的。Division+Office还是本平台编辑和控制。Unit资质认证剥离去处，本平台涉及单位详情审批。
 *来自哪一个监察机构的数据还一样呢？以监察为主动方。每一个省份对一个单位的管理资质证书可能有还不一样的有效范围，授权地域：监察审批设备以及相关证照。
 单位表实际是外部数据源，并非本平台自己家维护！现今Unit实际也受旧检验平台的注入，旧检验也做单位维护，本平台是集大成？如何协调状态多头数据控制者。
 单位资质证书问题只能给具体的控制方去做，监察平台必须自己审查单位资格证，各省地方监察对企业资质认定并非一致，本平台无法做到统一管辖。
 缺少? 已经被外部数据源删除了单位要删除码？
 监察删除某单位：PMT永久单位表依然有，普通单位表消失，而检验单位表状态是已注销，界面都能显示单位名称。
 不能用@Data+并且@Entity的产生: java.lang.StackOverflowError'  Cannot evaluate md.cm.unit.Unit.toString()
 [性能]针对性添加索引，提速5倍了：company_ID、person_ID两个是外键字段也需要添加索引啊，倒茬依据company/person查询unit,各添加个唯一性索引强劲改善性能！
 */
@AllArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(indexes={@Index(columnList = "company_id",unique=true), @Index(columnList = "person_id",unique=true) } )
@Builder(toBuilder=true)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region ="Slow")
public class Unit implements UnitPi{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    /* @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "commonSeq")
    @SequenceGenerator(name = "commonSeq", initialValue = 1, allocationSize = 1, sequenceName = "SEQUENCE_COMMON")
    protected Long id;  */

    /**单位已经注销，但是还需要显示名称啊；已经无效的单位，仅剩下名字有用。
     * */
    private Boolean cancel;  //单位已经被注销；cancelled; [企业解散？,个人死], 资格问题？
    //旧检验平台的Id  ? 其它省份咋整？
    //private Long  oldId;      //旧检验平台UNT_ID【对接福建省市场监督管理局】
    //旧监察平台 单位ID和旧检验平台ID也不同。
    //private Long  jcId;      //旧监察平台(普通单位表的)JC_UNT_ID; 两张特殊表：监察机构自己,检验机构。

    //加载方式修改影响很大。根据业务场景挑选。懒加载了若想关联内省查询会运行错误。
/*    @OneToMany(mappedBy = "owner")
    @ToString.Exclude
    private Set<Eqp> owns;*/
    //默认采用LAZY方式加载实体,懒加载时加了@Transactional的查询才能不报错，但是graphQL内省阶段是与入口函数分离的=还是报错。
    //一对多或多对多时，默认懒加载，graphQL遇到这个字段，若想要顺着关联查询下去，程序报错，等于有一种信息安全控制机制。
    //懒加载的坏处，该字段代码不能直接使用，必须绕道，从反向关系依据id倒着查。
/*
    @OneToMany(mappedBy = "mtu")
    @ToString.Exclude
    private Set<Eqp> maints;    //维保设备集合
*/

    /**企业或组织，映射：特种设备范畴的单位概念；
     * 这里company,person两个，若采用接口/微服务/Rest方式，实际上本地无需DB库表实体类，只需要外部大数据库no以及类型标识。
    //但我这里采用本地维护模式，Company和Person可以直接使用来关联,两个id，不需要类型标识。
    //1:1关联； Adminunit本id对应Town的ID； 本来应当这张表添加1:1关联id字段。
    //1 ：1关系，关系是本类来维护，添加外键指向对方实体表的主键；
    //本类来维护1：1缺省的字段关联名字；@JoinColumn(name = "townID我这一边的关联字段不一定是id", referencedColumnName = "ID是对方的ＩＤ")
    //对方Company类 根本不知道我方的存在感。搜索Company获得company_id来我这表做company_id索引的再次查询。
    */
    @OneToOne(cascade = CascadeType.ALL, fetch=FetchType.LAZY )
    @JoinColumn( referencedColumnName = "ID")
    private Company company;        //直接代替实体类继承模式，改做1:1关系。

    /**个人身份注册的单位，映射：特种设备范畴的单位概念对应物。
     * company person两个只能二选一，company字段非空的那么company就算优先。
     单位可以是Person，但是Person不一定算入单位，单位是应用系统的概念。
     hibernate.AnnotationException: Unable to create index (company) on table Unit: database column 'company' not found. orm.jpa.vendor
    */
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn( referencedColumnName = "ID")
    private Person person;        //直接代替实体类继承模式，改做1:1关系。


    /**行业性质: 树 INDUSTRY_PROP_COD：  单位细分种类；个人，行业
     INDUSTRY_PROP_COD树形式的一堆代码，最多3位代码，合计557个。
    减免收费的依据条件/使用单位的性质:INDUSTRY_PROP_COD==O82||INDUSTRY_PROP_COD==O821||,,树状编码，3层树叶。
     是否减半收费行业？.IF_REDUCEFEE=1；
      导入单位时刻，默认认定为个人Z01||length(a.UNT_NAME)<<3;
    外部平台来主导单位管理：性质 分类 资格 等属性(除了本平台主动管理的那些属性字段外就由同步外部【监察平台】数据来产生)。
    '' Z99 Z01 83 其他 Z 826(中小学) 701 C 702 470;    ?实际一半是为空的； http://27.151.117.66:9922/fjsei/js/common/submit.jsp?method=getIndustryPropDict&pValue=null
    绝大多数是 其它.其他的； 只为了设置优惠收费的依据
     */
    private String  indCod;

    //【监察关心】父单位：SUP_JCUNT_ID 机构类型：
    //:经济类型：ECONM_TYPE_COD个体户国有全资 外资 股份公司;
    //:单位性质: UNT_PROP_COD 事业单位 社会团体 行政机关 企业 UNT_PROP_COD_q行政机关;   ？行业管理部门列表;

   //单位状态：UNT_STATE_q注销;证书有效期起始时间：UCERT_EFF_DATE_BEGIN_q UCERT_EFF_DATE_END_q 重点单位设立原因：KEYUNT_REASON_TYPE
  //单位管理||机构备案》 》培训 评审 考试机构；
  //单位管理》》乡镇机构 设计单位 行业管理单位 监察 使用 制造 充装单位 检验机构  安装。。  需设安全管理机构 需设安全管理员

    /**遗留属性：MGE_DEPT_TYPE 管理部门类型； 为何区分：
     * 和Division,科室 没绝对挂钩。
    mtp=1 =2 没有本质区别，若=1 无需设置地址, =2 应当为分支机构设置地址但是若下挂部门也可以不设地址，mtp=0没有分支部门或机构。
    管理部门类型[使用单位才需要设置的]直接上数字类型；0:无内设， mtp=1 内设管理部门, mtp=2 内设分支机构；
     维保单位在监察平台设置了驻点。
    一个单位下多个设备如果出现不同的mtp 。 Division.branch=?;
    监察平台没有该字段MGE_DEPT_TYPE，监察平台很多都设置了安全管理部门的字段实际数据。
     */

/*  @Deprecated     //dvs[]size=0? 对应mtp=0; mtp1 or mtp2:  dvs.size>0; Division.branch决定mtp1,2;
    @Builder.Default
    private Byte  mtp=0;        //数据质量差! !， 个人也做内设分支机构？
    */

    /**细分分支机构和管理部门:  个人或使用单位的就没必要设置部门和科室。
     * 特检院的组织架构层次=Unit：某大业务部门/某某分院=Division。 部门再往底下就是科室=Office。
     *@EqualsAndHashCode.Exclude 解决报错！hashCode() StackOverflowError:死循环？
     *@ToString.Exclude 解决ToString StackOverflowError:错"!errorOutstanding" with message transform method call failed at JPLISAgent.c
     *原来在 Unit.dvs只加了 ，实际上做了graphQL的属性dvs{内省}其实还是报错的。
     *   @ToString.Exclude
     *   @EqualsAndHashCode.Exclude
    */
/*
    @OneToMany(mappedBy = "unit")
    @ToString.Exclude
    private Set<Division>  dvs;    //分支或部门集合
*/

    /**
     * 单位关联任务Task
     * */
/*
    @OneToMany(mappedBy = "servu")
    @ToString.Exclude
    private Set<Task> tasks;
*/

    /**
     * 单位关联业务Isp记录  服务对象(使用单位)
     * 记录Set条数 可能很多的;
     * todo://可只做单方向的关联，例如查询 Unit.isps 就不要使用了。但是from Isp Where servu=unitId可以做,多一道查。
     * */
/*
    @OneToMany(mappedBy = "servu")
    @ToString.Exclude
    private Set<Isp> isps;
*/

    //管辖区域：GXUNT_AREA_COD_q;单位所在区域：UNT_AREA_COD_q;
    //监察机构:每个级别行政机构都有独立的ID; +配套关系来计算级别或管辖-隶属权限，后端比较。
    //[行政机关的附加字段]到底是管哪个级别+那个县那个镇的，行政区划4个等级+1的； 用于提高搜索判定速度。
    /**行政许可区域：下面的几个区划组合字段。
     * 不一定行政机关才有设置该等字段； 其他类型单位也能设置：检验机构的授权区域，考试机构的法定地界，充装单位经营区域(级别区域形式),营业执照约定的经营区域。
     * 考试机构 和 充装单位, 检验机构，监察行政机关：必须要设置行政许可区域。 安装改造大修单位。。-考虑要设(跨省通用认证资质?分支机构?)。
     * 级别区域形式:: 垂直层级管理，不考虑支持:in[]Set<>多个区域聚合(有多个麻烦)。
     * 假如充装单位多个街道[]形式改成前端判定|人工。extPartJson{Adminunit.id1,id2..}扩充底下细节区域; ?多对多关联啊？复杂模式；
     * 监察机构如果直接按照地市级别注册一个Unit也可行的，某某地级市的特种设备监察部门{福州市市场监督管理局(设28个内设机构：（022）特种设备安全监察处)}。
     * 或者感觉人数太多了，也是可以区县为一个单位独立实体Unit;直接某一个县某一个区的市场监督管理。
     * 13个县（市）区市场监管局--高新区局：福建省福州市闽侯县科技东路高新区管委会 【特例，Adminunit没有成片连在一起的地理区域】
     * 福州市晋安区市场监督管理局内设机构：：设14个内设科室；{10.特种设备安全监察科}；福州市市场监督管理局晋安区局新店管理所
     * [总结] '福州市市场监督管理局' 设立一个Unit, 人员User->Unit{+部门+科室}。
     * 高新区有单独的 Adminunit; 自贸区没有Adminunit，监察权力分配的针对单位才有自贸区概念。
     [管理者]行政管辖区域：GXUNT_AREA_COD_q;或者是，[被管理者]普通单位注册地(行政隶属的管理区域)：UNT_AREA_COD_q;
     对比的是：company.ad 或 person.ad 是单位表旧数据该字段含义=单位所在地地址配套的地区码。
     自贸区也能单独设置Adminunit,自贸区和单位管理直接挂钩。可为某个Unit设置隶属的自贸区Adminunit但是unit.company.ad却是普通的地理上的区划,两个Adminunit不一致。
     若要指定unit属于某个自贸区管辖，就可配置普通单位的lare=自贸区Adminunit{含义：注册地级别，限定经营和授权区域}。？!应该搞多个Adminunit字段好区分用途。
     用了ManyToOne(fetch= FetchType.LAZY)的话：接口在去除@Transactional后才会报错LazyInitializationException:  - no Session
     * */
/*
    @ManyToOne(fetch= FetchType.EAGER)
    @JoinColumn
    @ToString.Exclude
    private Adminunit  lare;
*/

    //todo:应该搞多个Adminunit字段 [管理者]行政管辖区域 [被管理者]普通单位注册地(行政隶属的管理区域);

    //旧平台提供的：
    //地区代码 ->Address  pos() :{ad 行政区划}
    //@Deprecated
    private String  area;   //UntMge. UNT_AREA_COD   不一定 是最小的乡镇级别管理区域代码。


    //？父辈单位,父单位：SUP_JCUNT_ID
    //所属自贸区 ZMQ_COD：太复杂[in[]] ；放在 前端比较，人工校对区域级别的授权;
    //自贸区{检验平台没用到}ZMQ_COD：使用单位身份来说，该单位名下的特种设备行业:应该归属那个监察机构。自贸区是管理单位的不是针对设备的? 和单位相关的审批要找哪一个监察机构过关的？
    //private Unit incpu; 所属监察机构;     //自贸区
    //UNT_TYPE; UNTTYPES; 单位属性：【监察平台关注】 UNTTYPES=设计单位；旧平台？FROM TB_UNT_ASSUNTTYPE WHERE UNT_ID=
    //@Embeddable+@Embedded：直接扩充多个字段，等于重复利用嵌入部分实体多个字段的定义的做法，没啥用！
    //@Lob   private Integer[] ident; 直接转为longblob存储类型。
    //各种资质，归属单位类型。
    //@ElementCollection：private Set<String>  ident；自动生成unit_ident附属表的，做法实际上等于简化版本的@OneToMany注解，区别是不需要单独建立实体表，定义一个实体就足够，不用两个实体定义@Fetch(FetchMode.JOIN)。
    //@ElementCollection
    //@Fetch(FetchMode.JOIN)
    //private List<Qualification>  ident;   无法独立访问多端的实体表，多端不能单独管理搜索，多端只能依附于特定一个1方的实体id上操作。
    //监察合计有31种的单位类型代码。"UNT_TYPE", COMMENTS: "单位分类代码", DATA_TYPE: "NUMBER"
    //UNT_TYPE: "9", "维保单位", PARENT_UNT_TYPE: "-1"  "91", CLASS: "1",   "维保单位",
    private Boolean  maitu;  //维保单位
    //"11", text: "使用单位", parentCod: "1" , "1", text: "使用单位", parentCod: "-1"
    private Boolean  useu;   //使用单位 也需要资格管理？ 那些具体种类的设备允许使用还要区分拆开？
    //"2", text: "生产单位", parentCod: "-1", id: "22", text: "制造单位", parentCod: "2"
    private Boolean  makeu;   //制造单位
    //"2", text: "生产单位", parentCod: "-1", id:"23", text: "设计单位", parentCod: "2"
    private Boolean  desiu;   //设计单位
    //"2", text: "生产单位", parentCod: "-1","21", text: "安装改造维修单位", parentCod: "2",
    //8: {id: "211", text: "维修单位", parentCod: "21"}
    private Boolean  repau;
    //0: {id: "212", text: "安装单位", parentCod: "21"}
    private Boolean  instu;
    //1: {id: "213", text: "改造单位", parentCod: "21"}
    private Boolean  remou;
    //UNT_TYPE: "4", "监察机构", PARENT_UNT_TYPE: "-1"  "41",    "监察机构", PARENT_UNT_TYPE: "4"}
    private Boolean  supvu;
    //UNT_TYPE: "5", "检验检测机构", PARENT_UNT_TYPE: "-1"; UNT_TYPE: "51", "综合检验机构", PARENT_UNT_TYPE: "5"}
    private Boolean  ispu;
            //19: {UNT_TYPE: "53",     "无损检测机构", PARENT_UNT_TYPE: "5"}
    private Boolean  nondu;
            //21: {UNT_TYPE: "55",     "安全阀校验机构", PARENT_UNT_TYPE: "5"}
    private Boolean  valvu;
            //22: {UNT_TYPE: "57",     "自检检验机构", PARENT_UNT_TYPE: "5"}
    private Boolean  testu;     //检测？
    //UNT_TYPE: "3", "充装单位", PARENT_UNT_TYPE: "-1"} UNT_TYPE: "31", "充装单位", PARENT_UNT_TYPE: "3"}
    private Boolean  fillu;

    //@PostConstruct  不会执行到！
    //private void init() {    this.id=359L;  }
    public Unit() {
    }
    //无法手动id加载实体！
    //在graphQL接口函数直接上参数Unit类型的，DataFetchingEnvironment env无法用；
    public Unit(String id){
        //Class<T> classForName(String className)
        //SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory(); Could not locate cfg.xml resource [hibernate.cfg.xml]
        //SessionFactory sessionFactory=(new HibernateTemplate()).getSessionFactory();
        //Session session=sessionFactory.getCurrentSession();
        //HibernateDaoSupport.getHibernateTemplate();
        //this.getHibernateTemplate().get(entityClass, id);
        //Session session=CurrentSessionContext.currentSession();
        //Session session=SessionFactory.getCurrentSession();
    }


    //todo: MANAGE_UNT 上级的行业管理单位；行业管理单位=后妈；监察JC_UNT_MANAGE？管理下级单位，到底有啥权限职责威力，仅仅摆设或标记。

    //监察才有的， 所属自贸区ZMQ_COD，针对设备所属的单位 的上级 监管机构管辖。{行政割据概念}
/*    public Connection<Task> tasks(DataFetchingEnvironment env) {
        //需排序??
        return new MemoryListConnection(tasks.stream().collect(Collectors.toList())).get(env);
    }*/
    //该字段淘汰：避免多头维护数据，直接引用关联属性类company或person中的name字段,与Unit是1对1关系。
    //UNT_NAME
    /**造福前端 useu{id mtp company{id name no linkMen phone address} person{id name no phone address}},
     * 太啰嗦: useu{id mtp name no phone address linkMen},简化掉。
    * */
    @Transient
    public String name(){
        return(null!=company? company.getName() : null!=person? person.getName() : null);
    }
    //组织机构代码 or 身份证号
    @Transient
    public String no(){
        return(null!=company? company.getNo() : null!=person? person.getNo() : null);
    }
    @Transient
    public String phone(){
        return(null!=company? company.getPhone() : null!=person? person.getPhone() : null);
    }
    //多个应用系统对接：内部用ID挂接，数据库不同的亦即外部平台对接就需要依靠名字编码等唯一性过滤定位组合来判定。
    //UNT_ADDR
/*    @Transient
    public String address(){
        return(null!=company? company.address() : null!=person? person.address() : null);
    }*/
    //短路方式：直接提供嵌套管关联对象的属性对外暴露。
/*    @Transient
    public Address  pos(){
        return(null!=company? company.address() : null!=person? person.address() : null);
    }*/
    //实际应该改成是Person实体表ID; Person个人  UNT_LKMEN==自己
    //UNT_LKMEN
    @Transient
    public String linkMen(){
        return(null!=company? company.getLinkMen() : null!=person? person.getName() : null);
    }

    public String id() {
        return Tool.toGlobalId(this.getClass().getSimpleName(), String.valueOf(this.id));
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
        Unit unit = (Unit) o;
        return id != null && Objects.equals(id, unit.id);
    }

    @Override
    public int hashCode() {
        return (int) (getClass().hashCode() + id.hashCode());
    }


//    @CreatedBy
 //   private User user;
    @CreatedDate
    private Instant createdDate;
}



/*
把unit 使用单位，继续拆分成了：person + company 优化存储语义；前端界面提前区分两种unit。
company 法人单位,其他组织
person 个人
unit是业务代理者，company和person是通用的大数据基础库。
company和person两个实体类的库表数据很可能来自外部同步过来的，数据维护负责是其他方面的人。
*/
